﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.DataMining.ClusterAnalysis
{
    /// <summary>
    /// This class represents a cluster for cluster analysis.
    /// </summary>
    [Serializable]
    public class Cluster : ICluster
    {
        /// <summary>
        /// The current centroid position.
        /// </summary>
        private GeneralVector centroid;

        /// <summary>
        /// The previous centroid position.
        /// </summary>
        private GeneralVector oldCentroid;

        /// <summary>
        /// The members of the cluster.
        /// </summary>
        private readonly List<ClusterAnalysisItem> clusterMembers;

        /// <summary>
        /// Initializes a new instance of the <see cref="Cluster"/> class.
        /// </summary>
        /// <param name="centroid">The centroid of the cluster.</param>
        public Cluster(GeneralVector centroid)
        {
            if (centroid == (GeneralVector) null)
            {
                throw new ArgumentNullException("centroid");
            }

            this.centroid = centroid;
            this.clusterMembers = new List<ClusterAnalysisItem>();
        }

        /// <summary>
        /// Gets or sets the centroid of the cluster.
        /// </summary>
        /// <value>The centroid of the cluster.</value>
        public GeneralVector Centroid
        {
            get { return centroid; }
            set { centroid = value; }
        }

        /// <summary>
        /// Gets the members of the cluster.
        /// </summary>
        /// <value>The members of the cluster.</value>
        public List<ClusterAnalysisItem> ClusterMembers
        {
            get { return clusterMembers; }
        }

        /// <summary>
        /// Calculates the distance between the current position of the cluster and the specified item.
        /// </summary>
        /// <param name="item">The specified item.</param>
        /// <param name="distanceMeasureMode">The distance measure mode to use.</param>
        /// <returns>The distance between the current position of the cluster and the specified item.</returns>
        public double CalculateDistance(ClusterAnalysisItem item, DistanceCalculationMode distanceMeasureMode)
        {
            if (item == (ClusterAnalysisItem) null)
            {
                throw new ArgumentNullException("item");
            }

            switch (distanceMeasureMode)
            {
                case DistanceCalculationMode.Cosine:
                    return DistanceComputation.CosineDistance(this.centroid, item);

                case DistanceCalculationMode.Covariance:
                    return DistanceComputation.CovarianceDistance(this.centroid, item);

                case DistanceCalculationMode.DotProduct:
                    return DistanceComputation.DotProductDistance(this.centroid, item);

                case DistanceCalculationMode.Euclidean:
                    return DistanceComputation.EuclideanDistance(this.centroid, item);

                case DistanceCalculationMode.Manhattan:
                    return DistanceComputation.ManhattanDistance(this.centroid, item);
            }

            return 0;
        }

        /// <summary>
        /// Calculates the new centroid of the cluster.
        /// </summary>
        public void CalculateNewCentroid()
        {
            if (this.clusterMembers.Count == 0)
            {
                return;
            }

            GeneralVector tempuri = this.clusterMembers[0].Copy();

            this.oldCentroid = this.centroid.Copy();

            for (int i = 1; i < this.clusterMembers.Count; i++)
            {
                tempuri += this.clusterMembers[i];
            }

            for (int i = 0; i < tempuri.Count; i++)
            {
                tempuri[i] /= this.clusterMembers.Count;
            }

            this.centroid = tempuri;
        }

        /// <summary>
        /// Checks if the old and new position of the cluster is even.
        /// </summary>
        /// <param name="precision">The precision to use.</param>
        /// <returns>True if the positions are even otherwise, false.</returns>
        public bool NewCentroidEqualsOldCentroid(double precision)
        {
            if (this.oldCentroid == null)
            {
                return true;
            }

            for (int i = 0; i < this.centroid.Count; i++)
            {
                if (Math.Abs(this.oldCentroid[i] - this.centroid[i]) > precision)
                {
                    return false;
                }
            }

            return true;
        }
    }
}